package com.example.distributezklock.lock;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * @describte:Zookeeper分布式锁
 * @program:distribute-zk-lock
 * @author:zhengdp
 * @create:2021-09-22 18:00:44
 **/
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {

    // 用于连接Zookepeer服务
    private ZooKeeper zooKeeper;

    private String znode;

    public ZkLock() throws IOException {
        String host = "192.168.0.131:2181";
        this.zooKeeper = new ZooKeeper(host,60000,this);
    }

    /**
     * 获取分布式锁
     * @param businessCode
     * @return
     */
    public boolean getLock(String businessCode){
        try{
            // 1. 判断Zookepeer中该节点是否存在
            Stat exists = zooKeeper.exists("/" + businessCode, false);
            if(exists==null){
                // 2.创建业务根节点，根节点类型为持久类型，用于区分不同的业务
                zooKeeper.create("/"+businessCode,
                                businessCode.getBytes(),
                                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                                CreateMode.PERSISTENT);
            }

            // 3.创建瞬时有序节点  /order/order_0000001 ==> znode （与锁有关）
            znode = zooKeeper.create("/"+businessCode+"/"+businessCode+"_",
                            businessCode.getBytes(),
                            ZooDefs.Ids.OPEN_ACL_UNSAFE,
                            CreateMode.EPHEMERAL_SEQUENTIAL);
            // 4. 获取到根节点下的所有子节点
            List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false);
            Collections.sort(childrenNodes);
            String firstNode = childrenNodes.get(0);
            //5.1 判断 创建的瞬时节点是否为第一个子节点，如果是，则获得锁
            if(znode.endsWith(firstNode)){
                return true;
            }
            //5.2 如果不是第一个子节点，则创建的节点需要监听前一个节点
            String lastNode = firstNode;
            for(String node:childrenNodes){
                if(znode.endsWith(node)){
                    // 监听 lastNode是否存在，如果节点消息，监听器将会得到通知
                    zooKeeper.exists("/"+businessCode+"/"+lastNode,true);
                    break;
                }else{
                    lastNode = node;
                }
            }
            // 6. 等待 前面的节点消失（释放锁）
            synchronized (this){
                wait();
            }
            // 7. 被唤醒，获取到锁
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 释放锁
     * @throws Exception
     */
    @Override
    public void close() throws Exception {
        zooKeeper.delete(znode,-1);
        zooKeeper.close();
        log.info("我已经释放了锁！");
    }

    /**
     * Watcher方法
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        if(watchedEvent.getType() == Event.EventType.NodeDeleted){
            // 唤醒等待的线程 ,等待的线程获取锁
            synchronized (this){
                notify();
            }
        }
    }
}
